package hdgeom.primitives;

import java.awt.geom.Area;
import java.awt.geom.GeneralPath;
import java.awt.geom.Line2D;
import java.awt.geom.Path2D;
import java.util.List;
import java.util.Vector;

import processing.core.PGraphics;
import processing.core.PMatrix3D;
import processing.core.PVector;

public abstract class AbstractPolygon extends AbstractShape {
	public boolean closed=true;
	public abstract PVector getVertex(int i);

	public abstract float getX(int i);

	public abstract float getY(int i);

	public abstract void setPosition(int i, float x, float y);

	public Rect getBounds2D() {
		Rect bounds = new Rect();
		for (int i = 0; i < getNumVertices(); i++) {
			PVector v = getVertex(i);
			bounds.addPos2D(v.x, v.y);
		}
		return bounds;
	}

	public float getPerimeter() {
		float sum = 0;
		for (int i = 0; i < getNumVertices(); i++) {
			PVector n1 = getVertex(i);
			PVector n2 = getVertex((i + 1) % getNumVertices());
			sum += PVector.dist(n1, n2);
		}
		return sum;
	}

	public static float getArea(List<? extends PVector> points) {
		float area = 0;
		int j;
		for (int i = 0; i < points.size(); i++) {
			j = i + 1;
			if (j == points.size())
				j = 0;
			PVector p1 = points.get(i);
			PVector p2 = points.get(j);
			area += (p1.x + p2.x) * (p2.y - p1.y);
		}
		return area / 2f;
	}

	public static float getArea(float[] x, float[] y) {
		float area = 0;
		int j;
		for (int i = 0; i < x.length; i++) {
			j = i + 1;
			if (j == x.length)
				j = 0;
			float x1 = x[i];
			float y1 = y[i];
			float x2 = x[j];
			float y2 = y[j];
			area += (x1 + x2) * (y2 - y1);
		}
		return area / 2f;
	}

	public Area constructArea() {
		Path2D.Float path = new Path2D.Float();
		for (int i = 0; i < this.getNumVertices(); i++) {
			float[] coords = new float[3];
			this.getPosition(i, coords);
			if (i == 0) {
				path.moveTo(coords[0], coords[1]);
			} else {
				path.lineTo(coords[0], coords[1]);
			}
		}
		path.closePath();
		return new Area(path);
	}

	public float getArea() {
		float area = 0;
		int j;
		for (int i = 0; i < getNumVertices(); i++) {
			j = i + 1;
			if (j == getNumVertices())
				j = 0;
			PVector p1 = getVertex(i);
			PVector p2 = getVertex(j);
			area += (p1.x + p2.x) * (p2.y - p1.y);
		}
		return area / 2f;
	}

	public static PVector getGravityCenter(Vector<? extends PVector> nodes) {
		float sumArea = getArea(nodes) * 6;
		float cX = 0;
		float cY = 0;
		for (int i = 0; i < nodes.size(); i++) {
			PVector n1 = nodes.get(i);
			PVector n2 = nodes.get((i + 1) % nodes.size());
			float f = (n1.x * n2.y - n2.x * n1.y);
			cX += (n1.x + n2.x) * f;
			cY += (n1.y + n2.y) * f;
		}
		cX /= sumArea;
		cY /= sumArea;
		return new PVector(cX, cY);
	}

	public static float getPerimeter(Vector<? extends PVector> pts) {
		float perimeter = 0;
		for (int i = 0; i < pts.size(); i++) {
			PVector p1 = pts.get(i);
			PVector p2 = pts.get((i + 1) % pts.size());
			perimeter += PVector.dist(p1, p2);
		}
		return perimeter;
	}

	public void display3D(PGraphics g) {
		display2D(g);
	}

	public void display2D(PGraphics g) {
		float[] coords = new float[3];
		int nNodes = getNumVertices();
		if (!closed){
			g.beginShape();
			for (int i = 0; i < getNumVertices(); i++) {
				getPosition(i, coords);
				g.vertex(coords[0], coords[1]);
			}
			g.endShape();
			return;
		}
		
		
		if (nNodes == 4) {
			g.beginShape(PGraphics.QUADS);
			for (int i = 0; i < nNodes; i++) {
				getPosition(i, coords);
				g.vertex(coords[0], coords[1]);

			}
			g.endShape();
		} else if (nNodes == 3) {
			g.beginShape(PGraphics.TRIANGLES);
			for (int i = 0; i < nNodes; i++) {
				getPosition(i, coords);
				g.vertex(coords[0], coords[1]);
			}
			g.endShape();
		} else {
			g.beginShape();
			for (int i = 0; i < nNodes; i++) {
				getPosition(i, coords);
				g.vertex(coords[0], coords[1]);

			}
			g.endShape(PGraphics.CLOSE);

		}
	}

	public void display3DExtruded(PGraphics g, float ex, float ey, float ez) {
		float[] coords = new float[3];
		int nNodes = getNumVertices();
		if(!closed){
			
				g.beginShape(PGraphics.QUAD_STRIP);
				for (int i = 0; i < nNodes; i++) {
					getPosition(i, coords);
					g.vertex(coords[0], coords[1],coords[2]);
					g.vertex(coords[0] + ex, coords[1] + ey,coords[2]+ ez);
				}
				g.endShape();
			return;
		}
		g.beginShape(PGraphics.QUAD_STRIP);
		for (int i = 0; i <= nNodes; i++) {
			getPosition(i % nNodes, coords);
			g.vertex(coords[0], coords[1], 0);
			g.vertex(coords[0] + ex, coords[1] + ey, ez);
		}
		g.endShape();
	}
	public void display3DExtruded(PGraphics g, float z1,float z2) {
		float[] coords = new float[3];
		int nNodes = getNumVertices();
		if(!closed){
			g.beginShape(PGraphics.QUAD_STRIP);
			for (int i = 0; i < nNodes; i++) {
				getPosition(i, coords);
				g.vertex(coords[0], coords[1], z1);
				g.vertex(coords[0] , coords[1] , z2);
			}
			g.endShape();
			return;
		}
		g.beginShape(PGraphics.QUAD_STRIP);
		for (int i = 0; i <= nNodes; i++) {
			getPosition(i % nNodes, coords);
			g.vertex(coords[0], coords[1], z1);
			g.vertex(coords[0] , coords[1] , z2);
		}
		g.endShape();
	}

	public static void display3D(PGraphics g, Vector<? extends PVector> pts) {
		g.beginShape();
		for (int i = 0; i < pts.size(); i++) {
			PVector p = pts.get(i);
			g.vertex(p.x, p.y);
		}
		g.endShape(PGraphics.CLOSE);
	}

	public PVector getMaxCircle(int nX, int nY) {
		Rect bounds = this.getBounds2D();
		float dX = bounds.getDimX() / nX;
		float dY = bounds.getDimY() / nY;
		float maxD = 0;
		PVector maxP = null;
		for (int x = 0; x < nX; x++) {
			for (int y = 0; y < nY; y++) {
				float cX = bounds.getX1() + dX * x;
				float cY = bounds.getY1() + dY * y;
				if (contains(cX, cY)) {
					float cD = this.getClosestDist(cX, cY);
					if (cD > maxD) {
						maxD = cD;
						maxP = new PVector(cX, cY);
					}
				}
			}
		}
		return maxP;
	}

	public static float getClosestDistSq(float x, float y,
			Vector<? extends PVector> pts) {
		double minD = Double.MAX_VALUE;
		for (int i = 0; i < pts.size(); i++) {
			PVector p1 = pts.get(i);
			PVector p2 = pts.get((i + 1) % pts.size());
			double cD = Line2D.ptSegDistSq(p1.x, p1.y, p2.x, p2.y, x, y);
			if (cD < minD) {
				minD = cD;
			}
		}
		return (float) minD;
	}

	public float getClosestDistSq(float x, float y) {
		float minD = Float.MAX_VALUE;
		for (int i = 0; i < getNumVertices(); i++) {
			PVector p1 = getVertex(i);
			PVector p2 = getVertex((i + 1) % getNumVertices());
			float cD = (float) Line2D.ptSegDistSq(p1.x, p1.y, p2.x, p2.y, x, y);
			if (cD < minD) {
				minD = cD;
			}
		}
		return (float) minD;
	}

	public static float getClosestDist(float x, float y,
			Vector<? extends PVector> pts) {
		return (float) Math.sqrt(getClosestDistSq(x, y, pts));
	}

	public float getClosestDist(float x, float y) {
		return getClosestDist(x, y);
	}

	public PVector getClosestPt(float x, float y) {
		PVector closestP = null;
		float minD = Float.MAX_VALUE;
		for (int i = 0; i < getNumVertices(); i++) {
			PVector p = getVertex(i);
			float cD = distanceSqr(p.x, p.y, x, y);
			if (cD < minD) {
				minD = cD;
				closestP = p;
			}
		}
		return closestP;
	}

	public static PVector getClosestPt(float x, float y,
			Vector<? extends PVector> pts) {
		PVector closestP = null;
		float minD = Float.MAX_VALUE;
		for (int i = 0; i < pts.size(); i++) {
			PVector p = pts.get(i);
			float cD = distanceSqr(p.x, p.y, x, y);
			if (cD < minD) {
				minD = cD;
				closestP = p;
			}
		}
		return closestP;
	}

	public static boolean contains(float x, float y, List<? extends PVector> pts) {
		int intersections = 0;
		for (int i = 0; i < pts.size(); i++) {
			PVector p1 = pts.get(i);
			PVector p2 = pts.get((i + 1) % pts.size());
			if (p1.x > p2.x) {
				PVector temp = p2;
				p2 = p1;
				p1 = temp;
			}
			if (x >= p1.x && x < p2.x) {
				if (y < p1.y && y < p2.y) {
					intersections++;
				} else if (y < p1.y || y < p2.y) {
					float dX = p2.x - p1.x;
					float dY = p2.y - p1.y;
					float cDX = x - p1.x;
					float cY = (cDX / dX) * dY + p1.y;
					if (cY > y) {
						intersections++;
					}
				}
			}

		}
		if (intersections % 2 == 1)
			return true;
		return false;
	}

	public boolean contains(float x, float y) {
		int intersections = 0;
		for (int i = 0; i < getNumVertices(); i++) {
			PVector p1 = getVertex(i);
			PVector p2 = getVertex((i + 1) % getNumVertices());
			if (p1.x > p2.x) {
				PVector temp = p2;
				p2 = p1;
				p1 = temp;
			}
			if (x >= p1.x && x < p2.x) {
				if (y < p1.y && y < p2.y) {
					intersections++;
				} else if (y < p1.y || y < p2.y) {
					float dX = p2.x - p1.x;
					float dY = p2.y - p1.y;
					float cDX = x - p1.x;
					float factor = cDX / dX;
					// if (factor<0||factor>1)System.out.println("wrong");
					float cY = (cDX / dX) * dY + p1.y;
					if (cY > y) {
						intersections++;
					}
				}
			}

		}
		if (intersections % 2 == 1)
			return true;
		return false;
	}

	public static GeneralPath getGeneralPath(List<? extends PVector> pts) {
		GeneralPath path = new GeneralPath();
		for (int i = 0; i < pts.size(); i++) {
			PVector myPt = pts.get(i);
			if (i == 0)
				path.moveTo(myPt.x, myPt.y);
			else {
				path.lineTo(myPt.x, myPt.y);
			}
		}
		path.closePath();
		return path;
	}

	public GeneralPath getGeneralPath() {
		GeneralPath path = new GeneralPath();
		for (int i = 0; i < getNumVertices(); i++) {
			PVector myPt = getVertex(i);
			if (i == 0)
				path.moveTo(myPt.x, myPt.y);
			else
				path.lineTo(myPt.x, myPt.y);
		}
		path.closePath();
		return path;
	}

}
